package com.yth.utils;

import com.google.common.collect.MapMaker;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author yutianhong
 * @version 1.0
 * @since 2025/5/22 10:44
 */
public class ReflectUtils {
    private static final Map<Class<?>, Map<String, Field>> DECLARED_FIELDS_CACHE = new MapMaker()
            .weakKeys()
            .concurrencyLevel(16)
            .makeMap();

    /**
     * get fieldValue via reflect
     */
    public static <T> T getDeclaredFieldValue(Class<?> clazz, String fieldName, Object obj) {
        Field field = getDeclaredField(clazz, fieldName);
        try {
            return (T) field.get(obj);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Could not access field: " + e.getMessage());
        }
    }

    /**
     * set fieldValue via reflect
     */
    public static void setDeclaredFieldValue(Class<?> clazz, String fieldName, Object obj, Object newValue) {
        Field field = getDeclaredField(clazz, fieldName);
        try {
            field.set(obj, newValue);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Could not access field: " + e.getMessage());
        }
    }

    private static Field getDeclaredField(Class<?> clazz, String fieldName) {
        Field field = getDeclaredFields(clazz).get(fieldName);
        if (field == null) {
            throw new RuntimeException("field not found. fieldName: " + fieldName + ", clazz: " + clazz);
        }
        return field;
    }

    private static Map<String, Field> getDeclaredFields(Class<?> clazz) {
        return DECLARED_FIELDS_CACHE.computeIfAbsent(clazz,
                k -> {
                    Field[] declaredFields = clazz.getDeclaredFields();
                    if (declaredFields.length == 0) {
                        return Collections.emptyMap();
                    }
                    return Collections.unmodifiableMap(
                            Arrays.stream(declaredFields)
                                    .peek(field -> field.setAccessible(true))
                                    .collect(Collectors.toMap(Field::getName, Function.identity()))
                    );
                }
        );
    }

    private ReflectUtils() {
    }
}
